#pragma once

#include <string>
#include <glm/glm.hpp>

namespace Detail
{
	class ShaderPart
	{
	public:
		ShaderPart(unsigned int type); /// Type should be GL_VERTEX_SHADER, GL_FRAGMENT_SHADER or GL_GEOMETRY_SHADER
	
		void Load(std::string filename);///< Create the shader from a file, calls Create()
		virtual void Create(std::string source);///< Creates the shader from the source
		unsigned int GetObject() const;
	protected:
		unsigned int m_ShaderObject;
		const unsigned int m_Type;
	};

	class ShaderProgram
	{
	public:
		void Set() const;
		inline unsigned int GetProgram() const { return m_ProgramObject; }

		template<typename t> inline void SetUniform(std::string name, const t& value) const
		{
			SetUniform(GetUniformLocation(name), value);
		}

		unsigned int GetUniformLocation(std::string name) const;

		static void SetUniform(unsigned int location, float value);
		static void SetUniform(unsigned int location, const glm::vec2& value);
		static void SetUniform(unsigned int location, const glm::vec3& value);
		static void SetUniform(unsigned int location, const glm::vec4& value);
		static void SetUniform(unsigned int location, const glm::mat3& value);
		static void SetUniform(unsigned int location, const glm::mat4& value);
		static void SetUniform(unsigned int location, int value);
		static void SetUniform(unsigned int location, unsigned int value);
	protected:
		unsigned int m_ProgramObject = 0;
	};
}

class FragmentShader : public Detail::ShaderPart
{
public:
	FragmentShader();
	FragmentShader& operator=(FragmentShader s);
};

class VertexShader : public Detail::ShaderPart
{
public:
	VertexShader();
	VertexShader& operator=(VertexShader s);
};

class GeometryShader : public Detail::ShaderPart
{
public:
	GeometryShader();
	GeometryShader& operator=(GeometryShader s);
};

/** Compute Shaders are a bit special. As they are always the only stage, they are not part of the bigger
	Shader Object. Therefore they have their own programm object and everything.
**/
class ComputeShader : public Detail::ShaderPart, public Detail::ShaderProgram
{
public:
	ComputeShader();
	void Create(std::string source) override;
	void Run(glm::uvec3 workGroupSize);
private:
};


class Shader : public Detail::ShaderProgram
{
public:
	void Load(std::string vertexFile, std::string fragmentFile, std::string geometryFile="");
	void Create(std::string vertexSource, std::string fragmentSource, std::string geometrySource="");
	
	void BindTexture(std::string Name, unsigned int Texture, unsigned int Channel) const;
	void BindTexture(unsigned int Texture, unsigned int Channel) const;
private:
	void ProcessShader();///< is called after Shader::Load() or Shader::Create()
	VertexShader   m_VertexShader;
	FragmentShader m_FragmentShader;
	GeometryShader m_GeometryShader;
};
